import configparser
import os
import ftplib
import re
import shutil
import ssl
import time
from collections import namedtuple
from config import Config
from settings import ENCODING, BASE_DIR, conf
from utils.publicFun import load_obj, save_obj


class FtpLoginInfo:
    def __init__(self,
                 host='127.0.0.1',
                 port=21,
                 user='anonymous',
                 pwd=''):
        self.host = host.strip()
        self.port = port
        self.user = user.strip()
        self.pwd = pwd.strip()
        if user == '':
            self.user = 'anonymous'
            self.pwd = ''


FileInfo = namedtuple('FileInfo', ['name', 'size', 'date', 'chmod'])


class FtpClient(ftplib.FTP):
    __ftp = None
    __log_info = None
    __ftpsignal = None

    def __init__(self, textBrowserSignal = None):
        config = Config('ftp.ini', encoding=ENCODING)
        client_config = config['client']
        self.client_config = config['client']
        timeout = client_config.get('timeout', 20)
        encoding = client_config.get('encoding', 'gbk')
        debuglevel = client_config.get('debuglevel', 0)
        pasv = client_config.get('pasv', 0)
        ftplib.FTP.__init__(self, timeout=int(timeout))
        self._textBrowser_signal = textBrowserSignal
        FtpClient.__ftpsignal = textBrowserSignal
        self.encoding = 'utf8'#windows 用gbk,Linux用utf8,ftp().login会用到encoding并且文件传输时候用的编码和login时候是同一个
        self.set_debuglevel(int(debuglevel))
        self.set_pasv(bool(int(pasv)))
        # self.set_pasv(1)

    # def storbinary(self, cmd, fp, blocksize=8192, chunck_count=-1, callback=None, rest=None, chunck_rest=0):
    #     self.voidcmd('TYPE I')
    #     conn = self.transfercmd(cmd, rest)
    #     count = 0
    #     try:
    #         while 1:
    #             buf = fp.read(blocksize)
    #             if not buf:
    #                 break
    #             conn.sendall(buf)
    #             if callback:
    #                 callback(buf)
    #             # out chunck cout
    #             count += 1
    #             if count >= chunck_count > 0:
    #                 break
    #     finally:
    #         conn.close()
    #     return self.voidresp()
    @classmethod
    def is_connect(cls) -> bool:
        return cls.__log_info is not None
    def from_file_get(self,options,strings):
        config = configparser.ConfigParser()
        config.read(os.path.join(BASE_DIR, conf), encoding=ENCODING)
        return config.get(options, strings)
    def reconnect(self, options):
        # print('reconnect...')
        self.jobName = options
        user = self.from_file_get(options, 'user')
        pwd = self.from_file_get(options, 'pass')
        host = self.from_file_get(options, 'host')
        port = self.from_file_get(options, 'port')
        login_info = FtpLoginInfo(host, int(port), user, pwd)
        self.connect_login(login_info)

    def connect_login(self, log_info: FtpLoginInfo = None):
        try:
            self.connect(host=log_info.host, port=log_info.port)
            # print('连接ftp...')
            self.login(user=log_info.user, passwd=log_info.pwd)
            # print('登录ftp...')
        except Exception as e:
            print('连接问题:', e)
        ret = self.sendcmd('SYST')
        if 'Windows' in ret:

            self.encoding = 'gbk'

        FtpClient.__log_info = log_info

    def disconnect(self):
        with self:
            pass
        FtpClient.__log_info = None

    @staticmethod
    def ftp():
        # TODO 这个多线程并不安全,需要改进
        if FtpClient.__ftp is None or FtpClient.__ftpsignal is not None:
            # print('FtpClient.__ftp is None')
            FtpClient.__ftp = FtpClient(FtpClient.__ftpsignal)
        return FtpClient.__ftp

    def listdir(self, file_dir: str = '.') -> list:
        file_line_list = list()
        try:
            self.dir(file_dir, file_line_list.append)
        except ftplib.Error:
            pass
        file_list = list()
        for file_line in file_line_list:
            file = FtpClient.parse_line(file_line)
            file_list.append(file)
        return file_list

    @staticmethod
    def parse_line(line: str) -> FileInfo:
        data_list = list()
        data = ''
        for i in range(len(line)):
            ch = line[i]
            if ch != ' ':
                if len(data_list) == 8:
                    data_list.append(line[i:])
                    break
                data = data + ch
                continue
            if len(data) == 0:
                continue
            data_list.append(data)
            data = ''
        data_list[5] = ' '.join(data_list[5: 8])
        data_list[6] = data_list[8]
        data_list.pop()
        data_list.pop()
        return FileInfo(name=data_list[6], size=data_list[4], date=data_list[5], chmod=data_list[0])

    # 可以返回一个字典
    def dirs(self, parentDir='/'):
        dir_res = []
        self.dir(parentDir, dir_res.append)
        dirs = [{'dir_name': f.split(None, 8)[-1], 'datetime': f.split(None, 8)[0] + " " + f.split(None, 8)[1]} for f in
                dir_res if '<DIR>' in f or f.startswith('d')]
        return dirs

    def cwd(self, file_dir: str):
        parent_dir, name = os.path.split(file_dir)
        # print('9999999999', parent_dir, name)
        # parent_dir = ''
        if parent_dir == '' or parent_dir == '/':
            # return ftplib.FTP.cwd(self, '"'+file_dir+'"')
            return ftplib.FTP.cwd(self, file_dir)
        self.cwd(parent_dir)
        return ftplib.FTP.cwd(self, name)

    def retrlines(self, cmd, callback=None):
        """Retrieve data in line mode.  A new port is created for you.

        Args:
          cmd: A RETR, LIST, or NLST command.
          callback: An optional single parameter callable that is called
                    for each line with the trailing CRLF stripped.
                    [default: print_line()]

        Returns:
          The response code.
        """

        if callback is None:
            callback = print
        resp = self.sendcmd('TYPE A')
        with self.transfercmd(cmd) as conn, \
                conn.makefile('r', encoding=self.encoding, errors='ignore') as fp:
            while 1:
                line = fp.readline(self.maxline + 1)
                if len(line) > self.maxline:
                    raise ftplib.Error("got more than %d bytes" % self.maxline)
                if self.debugging > 2:
                    print('*retr*', repr(line))
                if not line:
                    break
                if line[-2:] == '\r\n':
                    line = line[:-2]
                elif line[-1:] == '\n':
                    line = line[:-1]
                callback(line)
            # shutdown ssl layer
            if ssl.SSLSocket is not None and isinstance(conn, ssl.SSLSocket):
                conn.unwrap()
        return self.voidresp()
    def mkd_dir(self, session, path_source, target_dir=None):
        current_dir = session.pwd()
        if target_dir.startswith('/'):
            target_dir = target_dir.replace('/', '', 1)

        # try:
        #     session.cwd(target_dir)  # 切换工作路径
        # except Exception as e:
        #     print('Except INFO:', e)
        base_dir, part_path = session.pwd(), target_dir.split('/')
        for subpath in part_path:
            # 针对类似  '/home/billing/scripts/zhf/send' 和 'home/billing/scripts/zhf/send' 两种格式的目录
            # 如果第一个分解后的元素是''这种空字符，说明根目录是从/开始，如果最后一个是''这种空字符，说明目录是以/结束
            # 例如 /home/billing/scripts/zhf/send/ 分解后得到 ['', 'home', 'billing', 'scripts', 'zhf', 'send', ''] 首位和尾都不是有效名称
            if '' == subpath:
                continue
            base_dir = os.path.join(base_dir, subpath).replace('\\', '/')  # base_dir + subpath + '/'  # 拼接子目录
            c_dir = session.pwd()
            try:
                session.cwd(base_dir)  # 切换到子目录, 不存在则异常
            except Exception as e:
                # print('Except INFO:', e)#failed to change directory
                # print('remote not exist directory %s , create it.' % (base_dir))
                session.mkd(base_dir)  # 不存在创建当前子目录 直到创建所有
                continue
        print('目录----', target_dir)
        try:
            session.cwd(current_dir)#回到原来
        except:
            pass;
        self.countSuccess()

    # def upload_dir(self, path_source, target_dir=None, is_realTime=False):
    #     session = self.__ftp
    #     files = os.listdir(path_source)
    #
    #     # 先记住之前在哪个工作目录中
    #     last_dir = os.path.abspath('.')
    #     # 然后切换到目标工作目录
    #     os.chdir(path_source)
    #
    #     if target_dir:
    #         current_dir = session.pwd()
    #         try:
    #             session.mkd(target_dir)
    #         except Exception:
    #             pass
    #         finally:
    #             if target_dir.startswith('/'):
    #                 target_dir = target_dir.replace('/', '', 1)
    #             session.cwd(os.path.join(current_dir, target_dir))
    #
    #     for file_name in files:
    #         current_dir = session.pwd()
    #         if os.path.isfile(path_source + r'/{}'.format(file_name)):
    #             self.upload_file(path_source, file_name, session)
    #         elif os.path.isdir(path_source + r'/{}'.format(file_name)):
    #
    #             current_dir = session.pwd()
    #             try:
    #                 session.mkd(current_dir + '/' + file_name)
    #             except Exception as e:
    #                 print(current_dir + '/' + file_name, '已存在或无权限创建目录')
    #                 pass
    #             session.cwd("%s/%s" % (current_dir, file_name))
    #             self.upload_dir(path_source + r'/{}'.format(file_name))
    #
    #         # 之前路径可能已经变更，需要再回复到之前的路径里
    #         session.cwd(current_dir)
    #
    #     os.chdir(last_dir)


    def upload_dir(self, path_source, target_dir=None, is_realTime=False):#path_source = local_dir,
        files = os.listdir(path_source)
        session = self.__ftp
        # print('ftpdir', session.pwd())
        # session.cwd('/')#550 could not change directory
        # session.cwd('/11222/test11223/test11223/test11223 - 副本有文件 - 副本 (2)/test11223-1')
        # return
        # session = FtpClient.__ftp
        # 先记住之前在哪个工作目录中
        last_dir = os.path.abspath('.')
        # 然后切换到目标工作目录
        os.chdir(path_source)
        # session.reconnect()
        current_dir = session.pwd()
        if target_dir:
            current_dir = session.pwd()
            try:
                session.mkd(target_dir)
            except Exception as e:
                pass

            finally:
                # print("target_dir...", target_dir)
                if target_dir.startswith('/'):
                    target_dir = target_dir.replace('/', '', 1)
                try:
                    session.cwd(os.path.join(current_dir, target_dir))#手动同步
                except:
                    pass
                pass
            # if is_realTime:
                # print('make dir----', target_dir)
                # self.countSuccess()
                # return
        for file_name in files:
            # current_dir = session.pwd()
            if os.path.isfile(path_source + r'/{}'.format(file_name)):
                self.upload_file(path_source, file_name, session, target_dir)
            elif os.path.isdir(path_source + r'/{}'.format(file_name)):

                current_dir = session.pwd()
                try:
                    session.mkd(file_name)

                except Exception as e:
                    # print('', file_name + '已存在', e)
                    pass
                print('目录----', current_dir, file_name)
                self.countSuccess()
                if current_dir == '/':
                    try:


                        session.cwd("%s%s" % (current_dir, file_name))  # 避免错误闪退
                    except:
                        # print('change directory11')
                        pass
                else:
                    try:
                        session.cwd("%s/%s" % (current_dir, file_name))
                    except:
                        # print('change dir')
                        pass
                newpath = path_source + r'/{}'.format(file_name)
                if os.listdir(newpath):
                    _target_dir = session.pwd()
                    self.upload_dir(newpath, _target_dir, is_realTime)#, session

            # 之前路径可能已经变更，需要再回复到之前的路径里
            try:
                session.cwd(current_dir)
            except:
                # print('change dir')
                pass;

        os.chdir(last_dir)

        # for file_name in files:
        # shutil.copytree(path_source + r'/{}'.format(file_name), path_source + '副本')
        # shutil.copy(path_source, path_source + '副本/' + time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())))

    def upload_file(self, path, file_name, session, target_dir=None, callback=None):#path=local_path
        # 记录当前 ftp 路径
        cur_dir = session.pwd()
        # print('cur_dir', cur_dir)
        if target_dir:
            try:
                session.mkd(target_dir)
            except Exception as e:
                pass
            finally:
                if target_dir.startswith('/'):
                    target_dir = target_dir.replace('/', '', 1)
                try:
                    session.cwd(os.path.join(cur_dir, target_dir))  # 手动
                except:
                    try:#添加嵌套文件及文件夹内容
                        target_dir = '/' + target_dir
                        if len('/'+target_dir) > len(cur_dir):
                            # target_dir = '/'+target_dir
                            if(cur_dir!='/'):
                                session.cwd(target_dir.replace(cur_dir, '', 1))
                            temp = session.pwd()
                            # print(session.pwd())
                        else:#if len('/' + target_dir) < len(cur_dir):
                            session.cwd(target_dir)
                    except Exception as e:
                        print('error-----', session.pwd(), e)
                        pass
                    pass#自动
        t0 = time.time()
        # print("path:%s \r\n\t   file_name:%s" % (path, file_name))
        if(path == path.split('/')[0] + '/_tmp_compress'+self.jobName):
            file = open(os.path.join(path, file_name.split('/')[-1]), 'rb')  # file to send 图片压缩零时文件
        else:
            file = open(os.path.join(path, file_name), 'rb')  # file to send

        is_date_dir_change = self.client_config.get("is_date_dir_change")
        if (is_date_dir_change == "1"):
            re_search = re.search(r'\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\d{3}', file_name)
            if (re_search):
                print('匹配时间-local-file', re_search.group(), file_name)
                file_name = file_name.replace(re_search.group(), "/".join(re_search.group().split("-")[0:4]))
        ret = session.storbinary('STOR %s' % file_name, file, callback = callback)  # send the file
        file.close()  # close file
        print('%s %s 用时%d秒' % (file_name, ret, time.time() - t0),
              time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
        self.countSuccess()
        try:
            session.cwd(cur_dir)
        except:
            pass
    def countSuccess(self):
        _cache_statistics = load_obj(self.jobName+"_cache_statistics")
        _cache_statistics["succeeded"] += 1
        save_obj(_cache_statistics, self.jobName+"_cache_statistics")
        FtpClient.__ftpsignal.emit(self.jobName)
